home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / var / lib / python-support / python2.6 / gnome_sudoku / sudoku.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-04-20  |  33.0 KB  |  997 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. import random
  5. import math
  6. import re
  7. from gettext import gettext as _
  8. GROUP_SIZE = 9
  9. TYPE_ROW = 0
  10. TYPE_COLUMN = 1
  11. TYPE_BOX = 2
  12. digit_set = range(1, GROUP_SIZE + 1)
  13. sets = [
  14.     digit_set] * 9
  15.  
  16. def is_set(row):
  17.     if len(row) == len(set(row)):
  18.         return True
  19.  
  20.  
  21. def is_sudoku(rows):
  22.     for r in rows:
  23.         if not is_set(r):
  24.             return False
  25.     
  26.     for i in range(len(rows[0])):
  27.         rw = [ r[i] for r in rows ]
  28.         if not is_set(rw):
  29.             return False
  30.     
  31.     width = int(math.sqrt(len(rows)))
  32.     box_coordinates = [ [
  33.         n * width,
  34.         (n + 1) * width] for n in range(width) ]
  35.     for x in box_coordinates:
  36.         for y in box_coordinates:
  37.             box = []
  38.             for ri in range(*y):
  39.                 pass
  40.             
  41.             if not is_set(box):
  42.                 return False
  43.         
  44.     
  45.     return True
  46.  
  47.  
  48. class UnsolvablePuzzle(TypeError):
  49.     pass
  50.  
  51.  
  52. class ConflictError(ValueError):
  53.     
  54.     def __init__(self, conflict_type, coordinates, value):
  55.         self.args = (conflict_type, coordinates, value)
  56.         self.type = conflict_type
  57.         self.coordinates = coordinates
  58.         self.x = coordinates[0]
  59.         self.y = coordinates[1]
  60.         self.value = value
  61.  
  62.  
  63.  
  64. class AlreadySetError(ValueError):
  65.     pass
  66.  
  67.  
  68. class SudokuGrid:
  69.     
  70.     def __init__(self, grid = False, verbose = False, group_size = 9):
  71.         self.grid = []
  72.         self.cols = []
  73.         self.rows = []
  74.         self.boxes = []
  75.         self.group_size = int(group_size)
  76.         self.verbose = False
  77.         self.gen_set = set(range(1, self.group_size + 1))
  78.         for n in range(self.group_size):
  79.             self.cols.append(set())
  80.             self.rows.append(set())
  81.             self.boxes.append(set())
  82.             self.grid.append([
  83.                 0] * self.group_size)
  84.         
  85.         self.box_by_coords = { }
  86.         self.box_coords = { }
  87.         self.calculate_box_coords()
  88.         self.row_coords = { }
  89.         for y in range(self.group_size):
  90.             pass
  91.         
  92.         self.col_coords = { }
  93.         for x in range(self.group_size):
  94.             pass
  95.         
  96.         self.verbose = verbose
  97.  
  98.     
  99.     def calculate_box_coords(self):
  100.         width = int(math.sqrt(self.group_size))
  101.         box_coordinates = [ [
  102.             n * width,
  103.             (n + 1) * width] for n in range(width) ]
  104.         box_num = 0
  105.         for xx in box_coordinates:
  106.             for yy in box_coordinates:
  107.                 self.box_coords[box_num] = []
  108.                 for x in range(*xx):
  109.                     for y in range(*yy):
  110.                         self.box_by_coords[(x, y)] = box_num
  111.                         self.box_coords[box_num].append((x, y))
  112.                     
  113.                 
  114.                 box_num += 1
  115.             
  116.         
  117.  
  118.     
  119.     def add(self, x, y, val, force = False):
  120.         if not val:
  121.             pass
  122.         
  123.         if self._get_(x, y):
  124.             if force:
  125.                 
  126.                 try:
  127.                     self.remove(x, y)
  128.                 print 'Strange: problem with add(', x, y, val, force, ')'
  129.                 import traceback
  130.                 traceback.print_exc()
  131.  
  132.             else:
  133.                 return None
  134.         force
  135.         if val in self.rows[y]:
  136.             raise ConflictError(TYPE_ROW, (x, y), val)
  137.         val in self.rows[y]
  138.         if val in self.cols[x]:
  139.             raise ConflictError(TYPE_COLUMN, (x, y), val)
  140.         val in self.cols[x]
  141.         box = self.box_by_coords[(x, y)]
  142.         if val in self.boxes[box]:
  143.             raise ConflictError(TYPE_BOX, (x, y), val)
  144.         val in self.boxes[box]
  145.         self.rows[y].add(val)
  146.         self.cols[x].add(val)
  147.         self.boxes[box].add(val)
  148.         self._set_(x, y, val)
  149.  
  150.     
  151.     def remove(self, x, y):
  152.         val = self._get_(x, y)
  153.         self.rows[y].remove(val)
  154.         self.cols[x].remove(val)
  155.         self.boxes[self.box_by_coords[(x, y)]].remove(val)
  156.         self._set_(x, y, 0)
  157.  
  158.     
  159.     def _get_(self, x, y):
  160.         return self.grid[y][x]
  161.  
  162.     
  163.     def _set_(self, x, y, val):
  164.         self.grid[y][x] = val
  165.  
  166.     
  167.     def possible_values(self, x, y):
  168.         return self.gen_set - self.rows[y] - self.cols[x] - self.boxes[self.box_by_coords[(x, y)]]
  169.  
  170.     
  171.     def pretty_print(self):
  172.         print 'SUDOKU'
  173.         for r in self.grid:
  174.             for i in r:
  175.                 print i,
  176.             
  177.             print 
  178.         
  179.         print 
  180.  
  181.     
  182.     def populate_from_grid(self, grid):
  183.         for y, row in enumerate(grid):
  184.             for x, cell in enumerate(row):
  185.                 if cell:
  186.                     self.add(x, y, cell)
  187.                     continue
  188.             
  189.         
  190.  
  191.     
  192.     def __repr__(self):
  193.         s = '<Grid\n       '
  194.         grid = []
  195.         for r in self.grid:
  196.             []([]([ str(i) for i in r ]))
  197.         
  198.         s += '\n       '.join(grid)
  199.         return s
  200.  
  201.     
  202.     def calculate_open_squares(self):
  203.         possibilities = { }
  204.         for x in range(self.group_size):
  205.             for y in range(self.group_size):
  206.                 if not self._get_(x, y):
  207.                     possibilities[(x, y)] = self.possible_values(x, y)
  208.                     continue
  209.             
  210.         
  211.         return possibilities
  212.  
  213.     
  214.     def find_conflicts(self, x, y, val, conflict_type = None):
  215.         '''Find all squares that conflict with value val at position x,y.
  216.         
  217.         If conflict_type is specified, we only find conflicts of given
  218.         type (ROW, COLUMN OR BOX).
  219.         '''
  220.         if conflict_type == TYPE_ROW:
  221.             coords = self.row_coords[y]
  222.         elif conflict_type == TYPE_COLUMN:
  223.             coords = self.col_coords[x]
  224.         elif conflict_type == TYPE_BOX:
  225.             coords = self.box_coords[self.box_by_coords[(x, y)]]
  226.         else:
  227.             coords = self.row_coords[y] + self.col_coords[x] + self.box_coords[self.box_by_coords[(x, y)]]
  228.         conflicting_coordinates = []
  229.         for x, y in coords:
  230.             if self._get_(x, y) == val:
  231.                 conflicting_coordinates.append((x, y))
  232.                 continue
  233.         
  234.         return conflicting_coordinates
  235.  
  236.     
  237.     def to_string(self):
  238.         '''Output our grid as a string.'''
  239.         return ' '.join([ []([ str(x) for x in row ]) for row in self.grid ])
  240.  
  241.  
  242.  
  243. def is_valid_puzzle(p):
  244.     """Check puzzle for basic validity.
  245.     
  246.     This does not check for solvability or ensure a unique
  247.     solution -- it merely checks well-formedness. This should
  248.     provide some protection again file corruption, etc. (i.e. if
  249.     we use this function to check puzzles before handing them out
  250.     to the rest of the app, we'll prevent tracebacks related to
  251.     corrupted puzzles).
  252.     """
  253.     
  254.     try:
  255.         p = p.replace(' ', '')
  256.         if not len(p.replace(' ', '')) == 81:
  257.             raise AssertionError
  258.         [ int(c) for c in p.replace(' ', '') ]
  259.     except:
  260.         return False
  261.  
  262.     return True
  263.  
  264.  
  265. def sudoku_grid_from_string(s):
  266.     '''Given an 81 character string, return a grid.'''
  267.     s = s.replace(' ', '')
  268.     if not len(s) <= GROUP_SIZE ** 2:
  269.         raise AssertionError
  270.     grid = []
  271.     i = 0
  272.     for x in range(GROUP_SIZE):
  273.         row = []
  274.         for y in range(GROUP_SIZE):
  275.             if len(s) <= i:
  276.                 n = 0
  277.             else:
  278.                 n = s[i]
  279.             
  280.             try:
  281.                 n = int(n)
  282.             except:
  283.                 if not n:
  284.                     pass
  285.                 n = 0
  286.  
  287.             if n in digit_set:
  288.                 row.append(n)
  289.             else:
  290.                 row.append(0)
  291.             i += 1
  292.         
  293.         grid.append(row)
  294.     
  295.     return SudokuGrid(grid)
  296.  
  297.  
  298. class SudokuSolver(SudokuGrid):
  299.     '''A SudokuGrid that can solve itself.'''
  300.     
  301.     def __init__(self, grid = False, verbose = False, group_size = 9):
  302.         self.current_guess = None
  303.         self.initialized = False
  304.         SudokuGrid.__init__(self, grid, verbose = verbose, group_size = group_size)
  305.         self.virgin = SudokuGrid(grid)
  306.         self.guesses = GuessList()
  307.         self.breadcrumbs = BreadcrumbTrail()
  308.         self.backtraces = 0
  309.         self.initialized = True
  310.         self.solved = False
  311.         self.trail = []
  312.  
  313.     
  314.     def auto_fill_for_xy(self, x, y):
  315.         '''Fill the square x,y if possible.'''
  316.         possible = self.gen_set - self.rows[y] - self.cols[x] - self.boxes[self.box_by_coords[(x, y)]]
  317.         changed = []
  318.         if len(possible) == 1:
  319.             val = possible.pop()
  320.             self.add(x, y, val)
  321.             return ((x, y), val)
  322.         if len(possible) == 0:
  323.             return -1
  324.         for coord_set, filled_set in [
  325.             (self.col_coords[x], self.cols[x]),
  326.             (self.row_coords[y], self.rows[y]),
  327.             (self.box_coords[self.box_by_coords[(x, y)]], self.boxes[self.box_by_coords[(x, y)]])]:
  328.             needed_set = self.gen_set - filled_set
  329.             for coord in coord_set:
  330.                 if self._get_(*coord):
  331.                     continue
  332.                     continue
  333.                 len(possible) == 0
  334.                 if (x, y) != coord:
  335.                     needed_set = needed_set - self.possible_values(*coord)
  336.                     continue
  337.                 len(possible) == 1
  338.             
  339.             if needed_set and len(needed_set) == 1:
  340.                 val = needed_set.pop()
  341.                 if val in possible:
  342.                     self.add(x, y, val)
  343.                     return ((x, y), val)
  344.                 return -1
  345.             len(needed_set) == 1
  346.             if len(needed_set) > 1:
  347.                 return -1
  348.         
  349.  
  350.     
  351.     def auto_fill(self):
  352.         changed = []
  353.         
  354.         try:
  355.             changed = self.fill_must_fills()
  356.         except UnsolvablePuzzle:
  357.             return changed
  358.  
  359.         
  360.         try:
  361.             changed.extend(self.fill_deterministically())
  362.         finally:
  363.             return changed
  364.  
  365.  
  366.     
  367.     def fill_must_fills(self):
  368.         changed = []
  369.         for label, coord_dic, filled_dic in [
  370.             ('Column', self.col_coords, self.cols),
  371.             ('Row', self.row_coords, self.rows),
  372.             ('Box', self.box_coords, self.boxes)]:
  373.             for n, coord_set in coord_dic.items():
  374.                 needs = []([ (n, False) for n in range(1, self.group_size + 1) ])
  375.                 for coord in coord_set:
  376.                     val = self._get_(*coord)
  377.                     if val:
  378.                         del needs[val]
  379.                         continue
  380.                     []
  381.                     for v in self.possible_values(*coord):
  382.                         if needs.has_key(v):
  383.                             if not needs[v]:
  384.                                 needs[v] = coord
  385.                             else:
  386.                                 del needs[v]
  387.                         needs[v]
  388.                     
  389.                 
  390.                 for n, coords in needs.items():
  391.                     if not coords:
  392.                         raise UnsolvablePuzzle('Missing a %s in %s' % (n, label))
  393.                     coords
  394.                     
  395.                     try:
  396.                         self.add(coords[0], coords[1], n)
  397.                         changed.append((coords, n))
  398.                     continue
  399.                     except AlreadySetError:
  400.                         dict
  401.                         dict
  402.                         raise UnsolvablePuzzle('%s,%s must be two values at once!' % coords)
  403.                         continue
  404.                     
  405.  
  406.                 
  407.             
  408.         
  409.         return changed
  410.  
  411.     
  412.     def fill_must_fills_2(self):
  413.         changed = []
  414.         for label, coord_dic, filled_dic in [
  415.             ('Column', self.col_coords, self.cols),
  416.             ('Row', self.row_coords, self.rows),
  417.             ('Box', self.box_coords, self.boxes)]:
  418.             for n, coord_set in coord_dic.items():
  419.                 needs = []([ (n, []) for n in range(1, self.group_size + 1) ])
  420.                 for coord in coord_set:
  421.                     val = self._get_(*coord)
  422.                     if val:
  423.                         del needs[val]
  424.                         continue
  425.                     []
  426.                     for v in self.possible_values(*coord):
  427.                         needs[v].append(coord)
  428.                     
  429.                 
  430.                 for n, coords in needs.items():
  431.                     if len(coords) == 0:
  432.                         raise UnsolvablePuzzle('Missing a %s in %s' % (n, label))
  433.                     len(coords) == 0
  434.                     if len(coords) == 1:
  435.                         
  436.                         try:
  437.                             self.add(coords[0][0], coords[0][1], n)
  438.                             changed.append((coords[0], n))
  439.                         except AlreadySetError:
  440.                             dict
  441.                             dict
  442.                             raise UnsolvablePuzzle('%s,%s must be two values at once!' % coords[0])
  443.                         except:
  444.                             dict<EXCEPTION MATCH>AlreadySetError
  445.                         
  446.  
  447.                     dict<EXCEPTION MATCH>AlreadySetError
  448.                 
  449.             
  450.         
  451.         return changed
  452.  
  453.     
  454.     def fill_must_fills_old(self):
  455.         """If we have a row where only one column can be a 3, it must be a 3.
  456.  
  457.         We raise an error if we discover an impossible situation.
  458.         We return a list of what we've changed
  459.         """
  460.         has_changed = []
  461.         for label, coord_dic, filled_dic in [
  462.             ('Column', self.col_coords, self.cols),
  463.             ('Row', self.row_coords, self.rows),
  464.             ('Box', self.box_coords, self.boxes)]:
  465.             for n, coord_set in coord_dic.items():
  466.                 values = filled_dic[n]
  467.                 needed = self.gen_set - values
  468.                 needed_dic = { }
  469.                 for c in needed:
  470.                     needed_dic[c] = []
  471.                 
  472.                 coord_set = (filter,)((lambda coords: not self._get_(*coords)), coord_set)
  473.                 for c in coord_set:
  474.                     pass
  475.                 
  476.                 if values != self.gen_set:
  477.                     raise UnsolvablePuzzle('Impossible to solve! We are missing %s in %s' % (self.gen_set - values, label))
  478.                 values != self.gen_set
  479.                 needed_filled_by = needed_dic.items()
  480.                 values_only_one_can_fill = filter((lambda x: len(x[1]) == 1), needed_filled_by)
  481.                 for v, coords in values_only_one_can_fill:
  482.                     coords = coords[0]
  483.                     has_changed.append([
  484.                         (coords[0], coords[1]),
  485.                         v])
  486.                     
  487.                     try:
  488.                         self.add(coords[0], coords[1], v)
  489.                     continue
  490.                     except AlreadySetError:
  491.                         None if self.verbose else None if self.verbose else []
  492.                         None if self.verbose else None if self.verbose else []
  493.                         if self._get_(coords[0], coords[1]) == v:
  494.                             pass
  495.                         else:
  496.                             raise UnsolvablePuzzle('Impossible to solve! %s,%s must be two values at once!' % coords)
  497.                         self._get_(coords[0], coords[1]) == v
  498.                     
  499.  
  500.                 
  501.             
  502.         
  503.         return has_changed
  504.  
  505.     
  506.     def scan_must_fills(self):
  507.         '''Scan to find how many fill_must_fills we could fill in with
  508.         100% positivity based on the grid as it currently stands (not
  509.         using the *cumulative* results'''
  510.         self.fake_add = True
  511.         self.fake_added = []
  512.         self.fill_must_fills()
  513.  
  514.     
  515.     def fill_deterministically(self):
  516.         poss = self.calculate_open_squares().items()
  517.         one_choice = filter((lambda x: len(x[1]) == 1), poss)
  518.         retval = []
  519.         for coords, choices in one_choice:
  520.             if self.verbose:
  521.                 print 'Deterministically adding ', coords, choices
  522.             
  523.             val = choices.pop()
  524.             self.add(coords[0], coords[1], val)
  525.             retval.append([
  526.                 (coords[0], coords[1]),
  527.                 val])
  528.         
  529.         if self.verbose:
  530.             print 'deterministically returning ', retval
  531.         
  532.         return retval
  533.  
  534.     
  535.     def solve(self):
  536.         self.auto_fill()
  537.         while not self.guess_least_open_square():
  538.             pass
  539.         if self.verbose:
  540.             print 'Solved!\n', self
  541.         
  542.         self.solved = True
  543.  
  544.     
  545.     def solution_finder(self):
  546.         self.auto_fill()
  547.         while not self.guess_least_open_square():
  548.             pass
  549.         self.solved = True
  550.         yield []([ tuple(r) for r in self.grid[0:] ])
  551.         []
  552.         for r in self.grid[0:]:
  553.             yield _[2](_[2][tuple(r)])
  554.             []
  555.             []
  556.         yield None
  557.         tuple
  558.  
  559.     
  560.     def has_unique_solution(self):
  561.         sf = self.solution_finder()
  562.         sf.next()
  563.         if sf.next():
  564.             return False
  565.         return True
  566.  
  567.     
  568.     def find_all_solutions(self):
  569.         solutions = set([])
  570.         sf = self.solution_finder()
  571.         sol = sf.next()
  572.         while sol:
  573.             solutions.add(sol)
  574.             sol = sf.next()
  575.         return solutions
  576.  
  577.     
  578.     def guess_least_open_square(self):
  579.         poss = self.calculate_open_squares().items()
  580.         if not poss:
  581.             if self.verbose:
  582.                 print 'Solved!'
  583.             
  584.             return True
  585.         poss.sort((lambda a, b: if not len(a[1]) > len(b[1]) or 1:
  586. if not len(a[1]) < len(b[1]) or -1:
  587. if not a[0] > b[0] or 1:
  588. if not a[1] < b[1] or -1:
  589. pass0))
  590.         least = poss[0]
  591.         possible_values = least[1] - self.guesses.guesses_for_coord(*least[0])
  592.         if not possible_values:
  593.             if self.breadcrumbs:
  594.                 self.backtraces += 1
  595.                 self.unwrap_guess(self.breadcrumbs[-1])
  596.                 return self.guess_least_open_square()
  597.             raise UnsolvablePuzzle('Unsolvable %s.\n                 Out of guesses for %s. Already guessed\n                 %s (other guesses are %s)' % (self, least[0], self.guesses.guesses_for_coord(*least[0]), self.guesses))
  598.         possible_values
  599.         guess = random.choice(list(possible_values))
  600.         guess_obj = Guess(least[0][0], least[0][1], guess)
  601.         if self.breadcrumbs:
  602.             self.breadcrumbs[-1].children.append(guess_obj)
  603.         
  604.         self.current_guess = None
  605.         self.add(least[0][0], least[0][1], guess)
  606.         self.current_guess = guess_obj
  607.         self.guesses.append(guess_obj)
  608.         self.trail.append(('+', guess_obj))
  609.         self.breadcrumbs.append(guess_obj)
  610.         
  611.         try:
  612.             filled = self.auto_fill()
  613.         except NotImplementedError:
  614.             self.trail.append('Problem filling coordinates after guess')
  615.             self.unwrap_guess(guess_obj)
  616.             return self.guess_least_open_square()
  617.  
  618.         if set([]) in self.calculate_open_squares().values():
  619.             self.trail.append('Guess leaves us with impossible squares.')
  620.             self.unwrap_guess(guess_obj)
  621.             return self.guess_least_open_square()
  622.  
  623.     
  624.     def unwrap_guess(self, guess):
  625.         self.trail.append(('-', guess))
  626.         if self._get_(guess.x, guess.y):
  627.             self.remove(guess.x, guess.y)
  628.         
  629.         for consequence in guess.consequences.keys():
  630.             if self._get_(*consequence):
  631.                 self.remove(*consequence)
  632.                 continue
  633.         
  634.         for child in guess.children:
  635.             self.unwrap_guess(child)
  636.             if child in self.guesses:
  637.                 self.guesses.remove(child)
  638.                 continue
  639.         
  640.         if guess in self.breadcrumbs:
  641.             self.breadcrumbs.remove(guess)
  642.         
  643.  
  644.     
  645.     def print_possibilities(self):
  646.         poss = self.calculate_open_squares()
  647.         poss_list = poss.items()
  648.         poss_list.sort((lambda a, b: if not len(a[1]) > len(b[1]) or 1:
  649. if not len(a[1]) < len(b[1]) or -1:
  650. pass0))
  651.         most_poss = len(poss_list[-1][1])
  652.         for y in range(len(self.cols)):
  653.             for x in range(len(self.rows)):
  654.                 print self.pad(val, most_poss + 2),
  655.             
  656.             for n in range(most_poss + 2):
  657.                 print 
  658.             
  659.         
  660.  
  661.     
  662.     def pad(self, n, pad_to):
  663.         n = str(n)
  664.         padding = int(pad_to) - len(n)
  665.         second_half = padding / 2
  666.         first_half = second_half + padding % 2
  667.         return ' ' * first_half + n + ' ' * second_half
  668.  
  669.     
  670.     def add(self, x, y, val, *args, **kwargs):
  671.         if self.current_guess:
  672.             self.current_guess.add_consequence(x, y, val)
  673.         
  674.         SudokuGrid.add(self, x, y, val, *args, **kwargs)
  675.  
  676.  
  677.  
  678. class InteractiveSudoku(SudokuSolver):
  679.     '''A subclass of SudokuSolver that provides some convenience
  680.     functions for helping along a human.who is in the midst of
  681.     solving.'''
  682.     
  683.     def __init__(self, grid = False, verbose = False, group_size = 9):
  684.         SudokuSolver.__init__(self, grid, verbose, group_size)
  685.  
  686.     
  687.     def to_string(self):
  688.         return self.virgin.to_string() + '\n' + SudokuSolver.to_string(self)
  689.  
  690.     
  691.     def find_impossible_implications(self, x, y):
  692.         '''Return a list of impossibilities implied by the users actions.'''
  693.         row_cells = self.row_coords[y]
  694.         col_cells = self.col_coords[x]
  695.         box = self.box_by_coords[(x, y)]
  696.         box_cells = self.box_coords[box]
  697.         for coord_set in [
  698.             row_cells,
  699.             col_cells,
  700.             box_cells]:
  701.             broken = []
  702.             coord_set = (filter,)((lambda coords: not self._get_(*coords)), coord_set)
  703.             for coords in coord_set:
  704.                 if not self.possible_values(*coords):
  705.                     broken.append(coords)
  706.                     continue
  707.             
  708.         
  709.         return broken
  710.  
  711.     
  712.     def check_for_completeness(self):
  713.         for r in self.rows:
  714.             if len(r) != self.group_size:
  715.                 return False
  716.         
  717.         for c in self.cols:
  718.             if len(c) != self.group_size:
  719.                 return False
  720.         
  721.         return True
  722.  
  723.     
  724.     def is_changed(self):
  725.         return self.grid != self.virgin.grid
  726.  
  727.  
  728.  
  729. class DifficultyRating:
  730.     very_hard = _('Very Hard')
  731.     hard = _('Hard')
  732.     medium = _('Medium')
  733.     easy = _('Easy')
  734.     very_hard_range = (0.75, 10)
  735.     hard_range = (0.6, 0.75)
  736.     medium_range = (0.45, 0.6)
  737.     easy_range = (-10, 0.45)
  738.     categories = {
  739.         'very hard': very_hard_range,
  740.         'hard': hard_range,
  741.         'medium': medium_range,
  742.         'easy': easy_range }
  743.     ordered_categories = [
  744.         'easy',
  745.         'medium',
  746.         'hard',
  747.         'very hard']
  748.     label_by_cat = {
  749.         'easy': easy,
  750.         'medium': medium,
  751.         'hard': hard,
  752.         'very hard': very_hard }
  753.     
  754.     def __init__(self, fill_must_fillables, elimination_fillables, guesses, backtraces, squares_filled):
  755.         self.fill_must_fillables = fill_must_fillables
  756.         self.elimination_fillables = elimination_fillables
  757.         self.guesses = guesses
  758.         self.backtraces = backtraces
  759.         self.squares_filled = squares_filled
  760.         if self.fill_must_fillables:
  761.             self.instant_fill_fillable = float(len(self.fill_must_fillables[0]))
  762.         else:
  763.             self.instant_fill_fillable = 0
  764.         if self.elimination_fillables:
  765.             self.instant_elimination_fillable = float(len(self.elimination_fillables[0]))
  766.         else:
  767.             self.instant_elimination_fillable = 0
  768.         self.proportion_instant_elimination_fillable = self.instant_elimination_fillable / self.squares_filled
  769.         self.proportion_instant_fill_fillable = self.instant_fill_fillable / self.squares_filled
  770.         self.elimination_ease = add_with_diminishing_importance(self.count_values(self.elimination_fillables))
  771.         self.fillable_ease = add_with_diminishing_importance(self.count_values(self.fill_must_fillables))
  772.         self.value = self.calculate()
  773.  
  774.     
  775.     def count_values(self, dct):
  776.         kk = dct.keys()
  777.         kk.sort()
  778.         return [ len(dct[k]) for k in kk ]
  779.  
  780.     
  781.     def calculate(self):
  782.         return (1 - float(self.fillable_ease) / self.squares_filled - float(self.elimination_ease) / self.squares_filled) + len(self.guesses) / self.squares_filled + self.backtraces / self.squares_filled
  783.  
  784.     
  785.     def __repr__(self):
  786.         return '<DifficultyRating %s>' % self.value
  787.  
  788.     
  789.     def pretty_print(self):
  790.         for name, stat in [
  791.             ('Number of moves instantly fillable by elimination', self.instant_elimination_fillable),
  792.             ('Percentage of moves instantly fillable by elimination', self.proportion_instant_elimination_fillable * 100),
  793.             ('Number of moves instantly fillable by filling', self.instant_fill_fillable),
  794.             ('Percentage of moves instantly fillable by filling', self.proportion_instant_fill_fillable * 100),
  795.             ('Number of guesses made', len(self.guesses)),
  796.             ('Number of backtraces', self.backtraces),
  797.             ('Ease by filling', self.fillable_ease),
  798.             ('Ease by elimination', self.elimination_ease),
  799.             ('Calculated difficulty', self.value)]:
  800.             print name, ': ', stat
  801.         
  802.  
  803.     
  804.     def value_string(self):
  805.         if self.value > self.very_hard_range[0]:
  806.             return _(self.very_hard)
  807.         if self.value > self.hard_range[0]:
  808.             return _(self.hard)
  809.         if self.value > self.medium_range[0]:
  810.             return _(self.medium)
  811.         return _(self.easy)
  812.  
  813.     
  814.     def value_category(self):
  815.         '''Get category string, without i18n or capitalization
  816.  
  817.         For use in categorizing category.
  818.         '''
  819.         if self.value > self.very_hard_range[0]:
  820.             return 'very hard'
  821.         if self.value > self.hard_range[0]:
  822.             return 'hard'
  823.         if self.value > self.medium_range[0]:
  824.             return 'medium'
  825.         return 'easy'
  826.  
  827.  
  828.  
  829. def get_difficulty_category_name(diff_float):
  830.     return DifficultyRating.label_by_cat.get(get_difficulty_category(diff_float), '?')
  831.  
  832.  
  833. def get_difficulty_category(diff_float):
  834.     for category, range in DifficultyRating.categories.items():
  835.         if diff_float <= diff_float:
  836.             pass
  837.         elif diff_float < range[1]:
  838.             return category
  839.     
  840.  
  841.  
  842. class SudokuRater(SudokuSolver):
  843.     
  844.     def __init__(self, grid = False, verbose = False, group_size = 9):
  845.         self.initialized = False
  846.         self.guessing = False
  847.         self.fake_add = False
  848.         self.fake_additions = []
  849.         self.filled = set([])
  850.         self.fill_must_fillables = { }
  851.         self.elimination_fillables = { }
  852.         self.tier = 0
  853.         SudokuSolver.__init__(self, grid, verbose, group_size)
  854.  
  855.     
  856.     def add(self, *args, **kwargs):
  857.         if not self.fake_add:
  858.             if self.initialized and not (self.guessing):
  859.                 self.scan_fillables()
  860.                 for delayed_args in self.add_me_queue:
  861.                     coords = (delayed_args[0], delayed_args[1])
  862.                     if not self._get_(*coords):
  863.                         SudokuSolver.add(self, *delayed_args)
  864.                         continue
  865.                 
  866.                 if not self._get_(args[0], args[1]):
  867.                     SudokuSolver.add(self, *args)
  868.                 
  869.                 self.tier += 1
  870.             else:
  871.                 SudokuSolver.add(self, *args, **kwargs)
  872.         else:
  873.             self.fake_additions.append(args)
  874.  
  875.     
  876.     def scan_fillables(self):
  877.         self.fake_add = True
  878.         self.fake_additions = []
  879.         
  880.         try:
  881.             self.fill_must_fills()
  882.         except:
  883.             pass
  884.  
  885.         self.fill_must_fillables[self.tier] = set(self.fake_additions[:]) - self.filled
  886.         self.add_me_queue = self.fake_additions[:]
  887.         self.fake_additions = []
  888.         
  889.         try:
  890.             self.fill_deterministically()
  891.         except:
  892.             pass
  893.  
  894.         self.elimination_fillables[self.tier] = set(self.fake_additions[:]) - self.filled
  895.         self.filled = self.filled | self.fill_must_fillables[self.tier] | self.elimination_fillables[self.tier]
  896.         self.add_me_queue.extend(self.fake_additions[:])
  897.         self.fake_add = False
  898.  
  899.     
  900.     def guess_least_open_square(self):
  901.         self.guessing = True
  902.         return SudokuSolver.guess_least_open_square(self)
  903.  
  904.     
  905.     def difficulty(self):
  906.         if not self.solved:
  907.             self.solve()
  908.         
  909.         self.clues = 0
  910.         (map,)((lambda r: (map,)((lambda i: if not i or 1:
  911. passsetattr(self, 'clues', self.clues.__add__(0))), r)
  912. ), self.virgin.grid)
  913.         self.numbers_added = self.group_size ** 2 - self.clues
  914.         rating = DifficultyRating(self.fill_must_fillables, self.elimination_fillables, self.guesses, self.backtraces, self.numbers_added)
  915.         return rating
  916.  
  917.  
  918.  
  919. class GuessList(list):
  920.     
  921.     def __init__(self, *guesses):
  922.         list.__init__(self, *guesses)
  923.  
  924.     
  925.     def guesses_for_coord(self, x, y):
  926.         return []([ guess.val for guess in ([], filter)((lambda guess: if guess.x == x:
  927. passguess.y == y), self) ])
  928.  
  929.     
  930.     def remove_children(self, guess):
  931.         removed = []
  932.         for g in guess.children:
  933.             if g in self:
  934.                 removed.append(g)
  935.                 self.remove(g)
  936.                 continue
  937.         
  938.         return removed
  939.  
  940.     
  941.     def remove_guesses_for_coord(self, x, y):
  942.         nuking = False
  943.         nuked = []
  944.         for i in range(len(self) - 1):
  945.             g = self[i - len(nuked)]
  946.             if g.x == x and g.y == y:
  947.                 nuking = True
  948.             
  949.             if nuking:
  950.                 self.remove(g)
  951.                 nuked += [
  952.                     g]
  953.                 continue
  954.         
  955.         return nuked
  956.  
  957.  
  958.  
  959. class BreadcrumbTrail(GuessList):
  960.     
  961.     def append(self, guess):
  962.         if self.guesses_for_coord(guess.x, guess.y):
  963.             raise ValueError('We already have crumbs on %s,%s' % (guess.x, guess.y))
  964.         self.guesses_for_coord(guess.x, guess.y)
  965.         list.append(self, guess)
  966.  
  967.  
  968.  
  969. class Guess:
  970.     
  971.     def __init__(self, x, y, val):
  972.         self.x = x
  973.         self.y = y
  974.         self.children = []
  975.         self.val = val
  976.         self.consequences = { }
  977.  
  978.     
  979.     def add_consequence(self, x, y, val):
  980.         self.consequences[(x, y)] = val
  981.  
  982.     
  983.     def __repr__(self):
  984.         s = '<Guess (%s,%s)=%s' % (self.x, self.y, self.val)
  985.         s += '>'
  986.         return s
  987.  
  988.  
  989.  
  990. def add_with_diminishing_importance(lst, diminish_by = (lambda x: x + 1)):
  991.     sum = 0
  992.     for i, n in enumerate(lst):
  993.         sum += float(n) / diminish_by(i)
  994.     
  995.     return sum
  996.  
  997.